﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Wyse.Server.Base;
using System.Net.Sockets;
using Wyse.Server.Logging;

namespace Wyse.Server.Game
{
    /// <summary>
    /// ClientContext for game clients
    /// </summary>
    public class GameClientContext : ClientContext, IMessageConsumer, IMessageProcessor
    {
        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="client"></param>
        public GameClientContext(TcpClient client, GameServerModule server)
            : base(client)
        {
            mServer = server;

            mParser = new MessageParser(this);
            mBuilder = new MessageBuilder(this);

            Socket.Blocking = true;
        }

        /// <summary>
        /// Gets or sets the player state
        /// </summary>
        public PlayerState PlayerState
        {
            get { return mPlayerState; }
            set
            {
                if (mPlayerState != null)
                    throw new InvalidOperationException("Cannot change the player state");
                mPlayerState = value;
            }
        }

        /// <summary>
        /// Runs the context (reads client messages)
        /// </summary>
        public override void Start()
        {
            try
            {
                byte[] receiveBuffer = new byte[MaxPayload];

                for (; ; )
                {
                    // incomplete data? break connection
                    if (Socket.Receive(receiveBuffer, 0, HeaderSize, SocketFlags.None) != HeaderSize)
                    {
                        Log.Info("Not enough data received. Closing connection with {0}", Socket.RemoteEndPoint);
                        break;
                    }

                    // extract the length from the header
                    int len = ParseHeader(receiveBuffer);
                    if (len == -1)
                    {
                        Log.Info("Malformed header received. Closing connection with {0}", Socket.RemoteEndPoint);
                        break;
                    }

                    // ignore messages exceeding payload limit
                    if (len > MaxPayload)
                    {
                        Log.Info("Payload size sent by {0} exceeded ({1})", Socket.RemoteEndPoint, len);
                        while (len > 0)
                        {
                            int torecv = Math.Min(len, receiveBuffer.Length);
                            Socket.Receive(receiveBuffer, 0, torecv, SocketFlags.None);
                            continue;
                        }
                    }

                    Socket.Receive(receiveBuffer, 0, len, SocketFlags.None);

                    lock (mSync)
                    {
                        mParser.ConsumeMessage(receiveBuffer, len);
                    }
                }

                // close the connection when finished processing
                Close();
            }
            catch (SocketException e)
            {
                Log.Info("Connection closed due to an exception {0}", e.Message);
            }
        }

        /// <summary>
        /// Sends the specified message
        /// </summary>
        /// <param name="message"></param>
        public void SendMessage(Message message)
        {
            lock (mSync)
            {
                mBuilder.ProcessMessage(message);
            }
        }

        /// <summary>
        /// Sends an error message
        /// </summary>
        /// <param name="code"></param>
        public void SendFailMessage(String code)
        {
            SendMessage(new Message(ServerActions.Fail, new ServerActions.FailParams(code)));
        }

        /// <summary>
        /// Parses the message header
        /// </summary>
        /// <param name="header"></param>
        /// <returns></returns>
        private int ParseHeader(byte[] header)
        {
            // check the protocol ID and version (WY01)
            for (int i = 0; i < 4; ++i)
            {
                if (sHeaderSignature[i] != header[i])
                    return -1;
            }

            int len = 0;
            int r = 1;

            // parse the length
            for (int i = 3; i >= 0; --i)
            {
                char c = (char)header[i + 4];
                int v;

                if (c >= '0' && c <= '9')
                    v = c - '0';
                else if (c >= 'a' && c <= 'f')
                    v = (c - 'a') + 10;
                else if (c >= 'A' && c <= 'F')
                    v = (c - 'A') + 10;
                else
                    return -1;

                len += (v * r);
                r *= 16;
            }

            return len;
        }

        /// <summary>
        /// Consumer - sends messages to the client
        /// </summary>
        /// <param name="message"></param>
        /// <param name="length"></param>
        void IMessageConsumer.ConsumeMessage(byte[] message, int length)
        {
            // prepare the message with header

            byte[] fullMessage = new byte[HeaderSize + length];
            Array.Copy(sHeaderSignature, 0, fullMessage, 0, sHeaderSignature.Length);

            String sizeStr = length.ToString("X4");
            Encoding.ASCII.GetBytes(sizeStr, 0, 4, fullMessage, 4);

            Array.Copy(message, 0, fullMessage, 8, length);

            Socket.Send(fullMessage, 0, fullMessage.Length, SocketFlags.None);
        }

        /// <summary>
        /// Processor - sends messages to the server
        /// </summary>
        /// <param name="msg"></param>
        void IMessageProcessor.ProcessMessage(Message msg)
        {
            lock (mSync)
            {
                if (mGame == null)
                {
                    // not connected to any game

                    if (msg.Action == ClientActions.Hello)
                    {
                        // hello message, determine which table the client wants to connect to

                        if (!msg.HasParametersOfType<ClientActions.HelloParams>())
                        {
                            SendFailMessage(FailCodes.InvalidMessage);
                            return;
                        }

                        String tableName;

                        ClientActions.HelloParams p = msg.GetParameters<ClientActions.HelloParams>();
                        if (String.IsNullOrWhiteSpace(p.Table))
                            tableName = "default";
                        else
                            tableName = p.Table;

                        mGame = mServer.GetTable(tableName);
                        if (mGame == null)
                        {
                            SendFailMessage(FailCodes.TableNotFound);
                            return;
                        }
                    }
                    else
                    {
                        SendFailMessage(FailCodes.InvalidState);
                        return;
                    }
                }

                // connected to a table - pass the message to game manager

                mGame.ProcessClientMessage(msg, this);
            }
        }

        private const int MaxPayload = 1024 * 32;
        private const int HeaderSize = 8;

        private static byte[] sHeaderSignature = new byte[] { (byte)'W', (byte)'Y', (byte)'0', (byte)'1' };

        private Object mSync = new object();

        private GameManager mGame;

        private PlayerState mPlayerState;

        private GameServerModule mServer;
        private MessageParser mParser;
        private MessageBuilder mBuilder;
    }
}
